package model.algorithms.nodesMapping;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;

import model.components.networks.Network;
import model.components.networks.sdn.SdnNetwork;
import model.components.networks.virtual.VirtualNetwork;
import model.components.nodes.comparator.AvailableVirtualNodesNumberComparator;
import model.components.nodes.comparator.NodeNumberComparator;
import model.components.nodes.node.Node;
import model.components.nodes.node.sdn.SdnNode;
import model.components.nodes.node.sdn.SdnRouter;
import model.components.nodes.node.sdn.SdnServer;
import model.components.nodes.node.sdn.SdnSwitch;
import model.components.nodes.node.virtual.VirtualNode;

public class Greedy extends NodesMappingAlgorithm
{
	public Greedy( String name )
	{
		super( name );
	}
	
	@Override
	public LinkedHashMap< VirtualNode, SdnNode > run( VirtualNetwork virtualNetwork, SdnNetwork sdnNetwork )
	{	
		LinkedHashMap< VirtualNode, SdnNode > nodesMapping = new LinkedHashMap< VirtualNode, SdnNode >();
		
		SdnNetwork copySdnNetwork = sdnNetwork.getCopy();
		double sdnNetworkAvailability[] = this.physicalNetworkAvailability( copySdnNetwork );
		double virtualNodesCapacities[][] = virtualNetwork.getNodesCapacities();
		double sdnNodesAvailableCapacities[][] = copySdnNetwork.getSdnNodesAvailableCapacities();
		
		String virtualNodesAttributes[][] = this.getNodesAttributes( virtualNetwork );
		String physicalNodesAttributes[][] = this.getNodesAttributes( copySdnNetwork );
		
		for( VirtualNode virtualNode: virtualNetwork.getVirtualNodes() )
		{
			double max = 0;
			SdnNode selectedPhysicalNode = null;
			 
			List< SdnNode> physicalNodes = null;
			
			if( virtualNode.getType().equals( Node.ROUTER_TYPE ) )
				physicalNodes = this.getRoutersSubstrateAvailability( copySdnNetwork );
			
			else if( virtualNode.getType().equals( Node.SWITCH_TYPE ) )
				physicalNodes = this.getSwitchesSubstrateAvailability( copySdnNetwork );
			
			else if( virtualNode.getType().equals( Node.SERVER_TYPE ) )
				physicalNodes = this.getServersSubstrateAvailability( copySdnNetwork );
						
			for( SdnNode PhysicalNode: physicalNodes )
			{
				int checkCapacities = 0;
				int checkAttributes = 0;
				 
				// Check that capacity constraints are satisfied for every functional type in the node.
				for( int i = 0; i < 3; i++ )
					if( sdnNodesAvailableCapacities[ i ][ PhysicalNode.getNumber() ] >= virtualNodesCapacities[ i ][ virtualNode.getNumber() ] )
						checkCapacities++;
				 
				for( int i = 0; i < 2; i++ )
					if( physicalNodesAttributes[ PhysicalNode.getNumber() ][ i ].equals( virtualNodesAttributes[ virtualNode.getNumber() ][ i ] ) )
						checkAttributes++;
				 
				// If capacity constraints are satisfied check if it has the biggest average
				if( checkCapacities == 3 && checkAttributes == 2 )
					if( sdnNetworkAvailability[ PhysicalNode.getNumber() ] > max )
					{
						max = sdnNetworkAvailability[ PhysicalNode.getNumber() ];
						selectedPhysicalNode = PhysicalNode;
					}
			}
			 
			// Update mapping and remove from pool of available substrate resources
			if( selectedPhysicalNode != null )
			{
				nodesMapping.put( virtualNode, selectedPhysicalNode );
				copySdnNetwork.getGraph().removeVertex( selectedPhysicalNode );
			}
			else
				break;
		}
		 
		if( nodesMapping.size() != virtualNetwork.getNodes().size() )
			nodesMapping = null;
		
		return nodesMapping;
	}

	public List< SdnNode > getRoutersSubstrateAvailability( SdnNetwork physicalNetwork )
	{	
		List< SdnNode > routersSubstrateAvailability = new ArrayList< SdnNode >();
		
		for( SdnNode nodeSubstrate: physicalNetwork.getSdnNodes() )
			if( nodeSubstrate instanceof SdnRouter )
				routersSubstrateAvailability.add( nodeSubstrate );
		
		Collections.sort( routersSubstrateAvailability, new NodeNumberComparator() );
		Collections.sort( routersSubstrateAvailability, new AvailableVirtualNodesNumberComparator() );
		
		return routersSubstrateAvailability;
	}
	public List< SdnNode > getSwitchesSubstrateAvailability( SdnNetwork physicalNetwork )
	{
		List< SdnNode > switchesSubstrateAvailability = new ArrayList< SdnNode >();
		
		for( SdnNode nodeSubstrate: physicalNetwork.getSdnNodes() )
			if( nodeSubstrate instanceof SdnSwitch )
				switchesSubstrateAvailability.add( nodeSubstrate );
		
		Collections.sort( switchesSubstrateAvailability, new NodeNumberComparator() );
		Collections.sort( switchesSubstrateAvailability, new AvailableVirtualNodesNumberComparator() );
		
		return switchesSubstrateAvailability;
	}
	public List< SdnNode > getServersSubstrateAvailability( SdnNetwork physicalNetwork )
	{
		List< SdnNode > serversSubstrateAvailability = new ArrayList< SdnNode >();
		
		for( SdnNode nodeSubstrate: physicalNetwork.getSdnNodes() )
			if( nodeSubstrate instanceof SdnServer )
				serversSubstrateAvailability.add( nodeSubstrate );
		
		Collections.sort( serversSubstrateAvailability, new NodeNumberComparator() );
		Collections.sort( serversSubstrateAvailability, new AvailableVirtualNodesNumberComparator() );
		
		return serversSubstrateAvailability;
	}
	
	public double[] physicalNetworkAvailability( SdnNetwork physicalNetwork )
	{	
		double availability[] = new double[ physicalNetwork.getSdnNodes().size() ];
			
		for( int i = 0; i < physicalNetwork.getSdnNodes().size(); i++ )
		{
			SdnNode substrateNode = physicalNetwork.getSdnNodes().get( i );
			availability[ i ] = substrateNode.getAvailableCPU() + substrateNode.getAvailableRAM();
			
			if( substrateNode instanceof SdnServer )
				availability[ i ] += substrateNode.getAvailableStorage();
		}
			
		//apart form node capacity take into consideration link capacity of links leaving the node
		//Multiply the capacities with sum of adjacent substrate links of every substrate node
		double bandwidthsAvailable[][] = physicalNetwork.getAvailableBWs();
		double bandwidthAdjacent[] = new double[ physicalNetwork.getSdnNodes().size() ];
		
		for( int i = 0; i < physicalNetwork.getSdnNodes().size(); i++ )
		{
			for( int j = 0; j < physicalNetwork.getSdnNodes().size(); j++ )
				if( bandwidthsAvailable[ i ][ j ] != 0 )
					bandwidthAdjacent[ i ] += bandwidthsAvailable[ i ][ j ];
			 
			availability[ i ] *= bandwidthAdjacent[ i ];
		}
		
		return availability;
	}
	
	public String[][] getNodesAttributes( Network network ) 
	{
		String nodesAttributes[][] = new String[ network.getGraph().getVertexCount() ][ 2 ];
		
		for( Node node: network.getGraph().getVertices() )
		{
			nodesAttributes[ node.getNumber() ][ 0 ] = node.getType();
			nodesAttributes[ node.getNumber() ][ 1 ] = node.getOS();
		}
		
		return nodesAttributes;
	}
}
